#include <linux/version.h>
#include <linux/syscalls.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/swap.h>
#include <linux/cpu.h>
#include <linux/sched.h>
#include <linux/syscalls.h>
#include <linux/tracepoint.h>
#include <linux/cgroup.h>
#include <linux/proc_fs.h>
#include <linux/delay.h>
#include <linux/kthread.h>

#include "sysak_mods.h"
#include "hook.h"
#include "proc.h"
#include "blackbox.h"

struct trace_sig_info {
	char comm[TASK_COMM_LEN];
	int pid;
	int sig;
}trace_info;

static int sig_ref;
#define BUFFER_LEN 256
static char process_info_buf[BUFFER_LEN];
static int tracesig_bid = -1;

static void save_process_info(struct task_struct *task, void *buf, int size)
{
	int ret;

	ret = snprintf(buf, size, "%s(%d)", task->comm, task->pid);
	if (ret <= 0)
		return;

	while (ret > 0  && task->parent && task->parent->pid > 1) {
		size = size - ret;
		if (size <= 1)
			break;
		task = task->parent;
		buf += ret;
		ret = snprintf(buf, size, "< %s(%d)", task->comm, task->pid);
	}
}

static void print_signal_info(struct task_struct *task, int sig)
{
	struct bbox_data_info data_info;
	int ret, len;

	memset(process_info_buf, 0, BUFFER_LEN);
	ret = snprintf(process_info_buf, BUFFER_LEN,"send sig %d to task %s[%d], generated by:",
                sig, task->comm, task->pid);
	if (ret <= 0 || ret >= (BUFFER_LEN - 1)) {
		printk("ret %d\n", ret);
		return;
	}

	save_process_info(current, process_info_buf + ret, BUFFER_LEN - ret);
	len = strlen(process_info_buf);
	process_info_buf[len] = '\n';
	data_info.data = process_info_buf;
	data_info.size = len + 1;
	bbox_write(tracesig_bid, &data_info);
}

#if KERNEL_VERSION(4, 19, 0) <= LINUX_VERSION_CODE
static void signal_generate_trace(void *ignore, int sig,
		struct siginfo *info, struct task_struct *task,
		int type, int result)
#elif KERNEL_VERSION(3, 10, 0) <= LINUX_VERSION_CODE
static void signal_generate_trace(void *ignore, int sig,
		struct siginfo *info, struct task_struct *task,
		int group, int result)
#else
static void signal_generate_trace(int sig,
		struct siginfo *info, struct task_struct *task,
		int group)
#endif
{
	if (trace_info.sig && trace_info.sig != sig)
		return;

	if (trace_info.pid && trace_info.pid != task->pid)
		return;

	if (strlen(trace_info.comm) && strcmp(trace_info.comm, task->comm))
		return;
	print_signal_info(task, sig);
}

static bool trace_enabled;
static void trace_sig_enable(void)
{
	if (trace_enabled)
		return;

	tracesig_bid = bbox_alloc("tracesig", BBOX_TYPE_RING);
	if (tracesig_bid < 0) {
		printk("bbox alloc failed,cannot enable\n");
		return;
	}

	hook_tracepoint("signal_generate", signal_generate_trace, NULL);
	trace_enabled = true;
	sysak_module_get(&sig_ref);
}

static void trace_sig_disable(void)
{
	if (!trace_enabled)
		return;

	unhook_tracepoint("signal_generate", signal_generate_trace, NULL);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 10, 0)
	synchronize_rcu();
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(4, 19, 0) || LINUX_VERSION_CODE <= KERNEL_VERSION(4, 17, 0)
	synchronize_sched();
#endif
	bbox_free(tracesig_bid);
	trace_enabled = false;
	sysak_module_put(&sig_ref);
}

static ssize_t signal_trace_write(struct file *file,
		const char __user *buf, size_t count, loff_t *offs)
{
	int ret;
	char cmd[256];
	char chr[256];
	int pid, sig;

	if (count < 1 || *offs)
		return -EINVAL;

	if (copy_from_user(chr, buf, 256))
		return -EFAULT;

	ret = sscanf(chr, "%255s", cmd);
	if (ret <= 0)
		return -EINVAL;

	if (strcmp(cmd, "comm") == 0) {
		ret = sscanf(chr, "comm %s", cmd);
		if (ret <= 0)
			return -EINVAL;
		strncpy(trace_info.comm, cmd, TASK_COMM_LEN);
		trace_info.comm[TASK_COMM_LEN - 1] = '\0';
	} else if (strcmp(cmd, "pid") == 0) {
		ret = sscanf(chr, "pid %d", &pid);
		if (ret <= 0)
			return -EINVAL;
		trace_info.pid = pid;
	} else if (strcmp(cmd, "sig") == 0) {
		ret = sscanf(chr, "sig %d", &sig);
		if (ret <= 0)
			return -EINVAL;
		trace_info.sig = sig;
	} else if (strcmp(cmd, "enable") == 0) {
		trace_sig_enable();
	} else if (strcmp(cmd, "disable") == 0) {
                trace_sig_disable();
        } else {
		return -EINVAL;
	}

	return count;
}

static int signal_trace_show(struct seq_file *m, void *v)
{
	seq_printf(m, "comm: %s\n", trace_info.comm);
	seq_printf(m, "pid: %d\n", trace_info.pid);
	seq_printf(m, "sig: %d\n", trace_info.sig);
	if (trace_enabled)
		bbox_ring_show(m, tracesig_bid);
	return 0;
}

static int signal_trace_open(struct inode *inode, struct file *filp)
{
	return single_open(filp, signal_trace_show, NULL);
}

static struct proc_dir_entry *signal_trace_proc;

#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 10, 0)
static const struct proc_ops signal_trace_fops = {
	.proc_open = signal_trace_open,
	.proc_read = seq_read,
	.proc_lseek = seq_lseek,
	.proc_write = signal_trace_write,
	.proc_release = single_release,
};
#else
const struct file_operations signal_trace_fops = {
	.open = signal_trace_open,
	.read = seq_read,
	.llseek = seq_lseek,
	.write = signal_trace_write,
	.release = single_release,
};
#endif

int trace_sig_init(void)
{
	signal_trace_proc = sysak_proc_create("sig_trace", &signal_trace_fops);
	
	return 0;
}

int trace_sig_exit(void)
{
	trace_sig_disable();
	return 0;
}

